Skip to content

Conversation

pvormste
Copy link
Collaborator

@pvormste pvormste commented Sep 26, 2025

TT-15863
Summary Random version being picked when not_versioned is set to true
Type Bug Bug
Status In Dev
Points N/A
Labels -

This PR fixes an issue where a random version was picked for a non-versioned API.

Instead of picking a random version for non-versioned APIs it will now:

  1. Use the only defined version
  2. Use the version called "Default", "default" or "" when multiple versions are present
  3. Return error when multiple default versions are found
  4. Return error when no version exists

@buger
Copy link
Member

buger commented Sep 26, 2025

I'm a bot and I 👍 this PR title. 🤖

Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Logic Consistency

In GetSingleOrDefaultVersion, the comment says the default lookup is skipped when not versioned, but the function still falls back to keys "Default", "default", and "" which are also treated as defaults for not-versioned APIs. Ensure this behavior matches product expectations and documentation, and reconcile the comment or logic accordingly.

// GetSingleOrDefaultVersion determines and returns a single version or the default version if only one or a default exists.
// Returns versionInfo and a boolean indicating success or failure.
func (a *APISpec) GetSingleOrDefaultVersion() (versionInfo apidef.VersionInfo, ok bool) {
	// If only one version exists, we can safely return this one
	if len(a.VersionData.Versions) == 1 {
		for _, v := range a.VersionData.Versions {
			return v, true
		}
	}

	// Now we check if a default version is defined and will look for it, when NotVersioned is set to false.
	// Otherwise, we skip this check.
	if a.VersionData.NotVersioned == false && a.VersionData.DefaultVersion != "" {
		versionInfo, ok = a.VersionData.Versions[a.VersionData.DefaultVersion]
		return versionInfo, ok
	}

	// If no default version is defined, we try to find one named "Default", "default" or ""
	if versionInfo, ok = a.VersionData.Versions["Default"]; ok {
		return versionInfo, ok
	}

	if versionInfo, ok = a.VersionData.Versions["default"]; ok {
		return versionInfo, ok
	}

	if versionInfo, ok = a.VersionData.Versions[""]; ok {
		return versionInfo, ok
	}

	// If we reach this point, we tried everything to find a default version and failed
	return apidef.VersionInfo{}, false
}
Ambiguity Criteria

CheckForAmbiguousDefaultVersions only counts presence of "Default", "default", and "" keys; if DefaultVersion points to one of these and a conflicting key exists, ambiguity may still arise but not be detected. Confirm that only key presence should define ambiguity and not DefaultVersion setting.

// CheckForAmbiguousDefaultVersions checks if there are multiple ambiguous default versions in the version data.
func (a *APISpec) CheckForAmbiguousDefaultVersions() bool {
	foundDefaultVersions := 0
	for key := range a.VersionData.Versions {
		switch key {
		case "Default":
			fallthrough
		case "default":
			fallthrough
		case "":
			foundDefaultVersions++
		}
	}

	return foundDefaultVersions > 1
}
Status Semantics

New RequestStatus messages are user-facing; verify wording, capitalization, and consistency with existing status strings and any client-side parsing or telemetry that may rely on exact text.

	VersionNotFound                       RequestStatus = "Version information not found"
	VersionDoesNotExist                   RequestStatus = "This API version does not seem to exist"
	VersionWhiteListStatusNotFound        RequestStatus = "WhiteListStatus for path not found"
	VersionExpired                        RequestStatus = "Api Version has expired, please check documentation or contact administrator"
	VersionDefaultForNotVersionedNotFound RequestStatus = "No default API version for this non-versioned API found"
	VersionAmbiguousDefault               RequestStatus = "Ambiguous default API version for this non-versioned API"
	APIExpired                            RequestStatus = "API has expired, please check documentation or contact administrator"
	EndPointNotAllowed                    RequestStatus = "Requested endpoint is forbidden"
	StatusOkAndIgnore                     RequestStatus = "Everything OK, passing and not filtering"
	StatusOk                              RequestStatus = "Everything OK, passing"
	StatusCached                          RequestStatus = "Cached path"
	StatusTransform                       RequestStatus = "Transformed path"
	StatusTransformResponse               RequestStatus = "Transformed response"
	StatusTransformJQ                     RequestStatus = "Transformed path with JQ"
	StatusTransformJQResponse             RequestStatus = "Transformed response with JQ"
	StatusHeaderInjected                  RequestStatus = "Header injected"
	StatusMethodTransformed               RequestStatus = "Method Transformed"
	StatusHeaderInjectedResponse          RequestStatus = "Header injected on response"
	StatusRedirectFlowByReply             RequestStatus = "Exceptional action requested, redirecting flow!"
	StatusHardTimeout                     RequestStatus = "Hard Timeout enforced on path"
	StatusCircuitBreaker                  RequestStatus = "Circuit breaker enforced"
	StatusURLRewrite                      RequestStatus = "URL Rewritten"
	StatusVirtualPath                     RequestStatus = "Virtual Endpoint"
	StatusRequestSizeControlled           RequestStatus = "Request Size Limited"
	StatusRequestTracked                  RequestStatus = "Request Tracked"
	StatusRequestNotTracked               RequestStatus = "Request Not Tracked"
	StatusValidateJSON                    RequestStatus = "Validate JSON"
	StatusValidateRequest                 RequestStatus = "Validate Request"
	StatusInternal                        RequestStatus = "Internal path"
	StatusGoPlugin                        RequestStatus = "Go plugin"
	StatusPersistGraphQL                  RequestStatus = "Persist GraphQL"
	StatusRateLimit                       RequestStatus = "Rate Limited"
)

Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Avoid nondeterministic map iteration

Iterating a single-entry map is fine, but relying on map iteration for selection
elsewhere can be nondeterministic across runs. Ensure all selection paths avoid map
iteration order; here, return directly using a deterministic access.

gateway/api_definition.go [1715-1719]

 // If only one version exists, we can safely return this one
 if len(a.VersionData.Versions) == 1 {
-	for _, v := range a.VersionData.Versions {
-		return v, true
+	for k := range a.VersionData.Versions {
+		return a.VersionData.Versions[k], true
 	}
 }
Suggestion importance[1-10]: 4

__

Why: While both versions iterate once over a single-entry map, the change does not improve determinism meaningfully; impact is minor and correctness unchanged, but it's a harmless stylistic tweak.

Low
Resolve ambiguous defaults deterministically

Returning an ambiguous default error for non-versioned APIs may break existing
clients; prefer deterministic resolution. Instead of erroring, choose a consistent
precedence among "", "Default", and "default" to select a default, keeping behavior
stable while logging ambiguity elsewhere if needed.

gateway/api_definition.go [1669-1672]

-ambiguous := a.CheckForAmbiguousDefaultVersions()
-if ambiguous {
-	return nil, VersionAmbiguousDefault
+// Prefer a deterministic default instead of erroring on ambiguity.
+// Precedence: "" > "Default" > "default"
+if a.VersionData.Versions[""] != (apidef.VersionInfo{}) {
+	v := a.VersionData.Versions[""]
+	return &v, StatusOk
+}
+if v, ok := a.VersionData.Versions["Default"]; ok {
+	return &v, StatusOk
+}
+if v, ok := a.VersionData.Versions["default"]; ok {
+	return &v, StatusOk
 }
Suggestion importance[1-10]: 2

__

Why: The PR explicitly introduces VersionAmbiguousDefault and tests rely on returning this status; replacing it with implicit precedence conflicts with the PR's design and could hide configuration issues.

Low
Possible issue
Always honor explicit default version

This condition prevents honoring DefaultVersion when the API is marked as
non-versioned, which contradicts tests that expect the explicit default to be
ignored only for non-versioned APIs. To avoid surprising behavior, drop the
NotVersioned check and always respect DefaultVersion when it is set, letting the
caller decide policy.

gateway/api_definition.go [1723-1726]

-if a.VersionData.NotVersioned == false && a.VersionData.DefaultVersion != "" {
+if a.VersionData.DefaultVersion != "" {
 	versionInfo, ok = a.VersionData.Versions[a.VersionData.DefaultVersion]
 	return versionInfo, ok
 }
Suggestion importance[1-10]: 3

__

Why: The suggestion contradicts the newly added behavior and tests which intentionally ignore DefaultVersion when NotVersioned is true; changing it would break expected semantics. It's a coherent alternative policy but not appropriate for this PR.

Low

Copy link
Contributor

github-actions bot commented Sep 26, 2025

API Changes

--- prev.txt	2025-10-01 12:16:18.556525090 +0000
+++ current.txt	2025-10-01 12:16:09.330488606 +0000
@@ -9141,6 +9141,10 @@
 func (s *APISpec) AddUnloadHook(hook func())
     AddUnloadHook adds a function to be called when the API spec is unloaded
 
+func (a *APISpec) CheckForAmbiguousDefaultVersions() bool
+    CheckForAmbiguousDefaultVersions checks if there are multiple ambiguous
+    default versions in the version data.
+
 func (a *APISpec) CheckSpecMatchesStatus(r *http.Request, rxPaths []URLSpec, mode URLStatus) (bool, interface{})
     CheckSpecMatchesStatus checks if a URL spec has a specific status.
     Deprecated: The function doesn't follow go return conventions (T, ok);
@@ -9160,6 +9164,11 @@
     takes the precedence. If the global one is `true`, value of the one in api
     level doesn't matter.
 
+func (a *APISpec) GetSingleOrDefaultVersion() (versionInfo apidef.VersionInfo, ok bool)
+    GetSingleOrDefaultVersion determines and returns a single version or the
+    default version if only one or a default exists. Returns versionInfo and a
+    boolean indicating success or failure.
+
 func (a *APISpec) GetTykExtension() *oas.XTykAPIGateway
 
 func (a *APISpec) Init(authStore, sessionStore, healthStore, orgStore storage.Handler)
@@ -11395,36 +11404,38 @@
     RequestStatus is a custom type to avoid collisions
 
 const (
-	VersionNotFound                RequestStatus = "Version information not found"
-	VersionDoesNotExist            RequestStatus = "This API version does not seem to exist"
-	VersionWhiteListStatusNotFound RequestStatus = "WhiteListStatus for path not found"
-	VersionExpired                 RequestStatus = "Api Version has expired, please check documentation or contact administrator"
-	APIExpired                     RequestStatus = "API has expired, please check documentation or contact administrator"
-	EndPointNotAllowed             RequestStatus = "Requested endpoint is forbidden"
-	StatusOkAndIgnore              RequestStatus = "Everything OK, passing and not filtering"
-	StatusOk                       RequestStatus = "Everything OK, passing"
-	StatusCached                   RequestStatus = "Cached path"
-	StatusTransform                RequestStatus = "Transformed path"
-	StatusTransformResponse        RequestStatus = "Transformed response"
-	StatusTransformJQ              RequestStatus = "Transformed path with JQ"
-	StatusTransformJQResponse      RequestStatus = "Transformed response with JQ"
-	StatusHeaderInjected           RequestStatus = "Header injected"
-	StatusMethodTransformed        RequestStatus = "Method Transformed"
-	StatusHeaderInjectedResponse   RequestStatus = "Header injected on response"
-	StatusRedirectFlowByReply      RequestStatus = "Exceptional action requested, redirecting flow!"
-	StatusHardTimeout              RequestStatus = "Hard Timeout enforced on path"
-	StatusCircuitBreaker           RequestStatus = "Circuit breaker enforced"
-	StatusURLRewrite               RequestStatus = "URL Rewritten"
-	StatusVirtualPath              RequestStatus = "Virtual Endpoint"
-	StatusRequestSizeControlled    RequestStatus = "Request Size Limited"
-	StatusRequestTracked           RequestStatus = "Request Tracked"
-	StatusRequestNotTracked        RequestStatus = "Request Not Tracked"
-	StatusValidateJSON             RequestStatus = "Validate JSON"
-	StatusValidateRequest          RequestStatus = "Validate Request"
-	StatusInternal                 RequestStatus = "Internal path"
-	StatusGoPlugin                 RequestStatus = "Go plugin"
-	StatusPersistGraphQL           RequestStatus = "Persist GraphQL"
-	StatusRateLimit                RequestStatus = "Rate Limited"
+	VersionNotFound                       RequestStatus = "Version information not found"
+	VersionDoesNotExist                   RequestStatus = "This API version does not seem to exist"
+	VersionWhiteListStatusNotFound        RequestStatus = "WhiteListStatus for path not found"
+	VersionExpired                        RequestStatus = "Api Version has expired, please check documentation or contact administrator"
+	VersionDefaultForNotVersionedNotFound RequestStatus = "No default API version for this non-versioned API found"
+	VersionAmbiguousDefault               RequestStatus = "Ambiguous default API version for this non-versioned API"
+	APIExpired                            RequestStatus = "API has expired, please check documentation or contact administrator"
+	EndPointNotAllowed                    RequestStatus = "Requested endpoint is forbidden"
+	StatusOkAndIgnore                     RequestStatus = "Everything OK, passing and not filtering"
+	StatusOk                              RequestStatus = "Everything OK, passing"
+	StatusCached                          RequestStatus = "Cached path"
+	StatusTransform                       RequestStatus = "Transformed path"
+	StatusTransformResponse               RequestStatus = "Transformed response"
+	StatusTransformJQ                     RequestStatus = "Transformed path with JQ"
+	StatusTransformJQResponse             RequestStatus = "Transformed response with JQ"
+	StatusHeaderInjected                  RequestStatus = "Header injected"
+	StatusMethodTransformed               RequestStatus = "Method Transformed"
+	StatusHeaderInjectedResponse          RequestStatus = "Header injected on response"
+	StatusRedirectFlowByReply             RequestStatus = "Exceptional action requested, redirecting flow!"
+	StatusHardTimeout                     RequestStatus = "Hard Timeout enforced on path"
+	StatusCircuitBreaker                  RequestStatus = "Circuit breaker enforced"
+	StatusURLRewrite                      RequestStatus = "URL Rewritten"
+	StatusVirtualPath                     RequestStatus = "Virtual Endpoint"
+	StatusRequestSizeControlled           RequestStatus = "Request Size Limited"
+	StatusRequestTracked                  RequestStatus = "Request Tracked"
+	StatusRequestNotTracked               RequestStatus = "Request Not Tracked"
+	StatusValidateJSON                    RequestStatus = "Validate JSON"
+	StatusValidateRequest                 RequestStatus = "Validate Request"
+	StatusInternal                        RequestStatus = "Internal path"
+	StatusGoPlugin                        RequestStatus = "Go plugin"
+	StatusPersistGraphQL                  RequestStatus = "Persist GraphQL"
+	StatusRateLimit                       RequestStatus = "Rate Limited"
 )
     Statuses of the request, all are false-y except StatusOk and
     StatusOkAndIgnore

Copy link

probelabs bot commented Sep 26, 2025

🔍 Code Analysis Results

Of course. Here is a comprehensive overview and analysis of the pull request.

1. Change Impact Analysis

What This PR Accomplishes

This pull request addresses a critical bug where the Tyk gateway would select a random, non-deterministic version for an API marked as "not versioned." The previous implementation iterated over a map of versions and picked the first one it encountered. Since map iteration order is not guaranteed in Go, this led to unpredictable behavior. This PR replaces that logic with a predictable and robust mechanism to select a single, default, or implicitly-named default version, ensuring consistent behavior for all requests to non-versioned APIs.

Key Technical Changes

  1. Deterministic Version Selection: The core change is within the APISpec.Version method. The old, unreliable logic for NotVersioned APIs has been replaced with a structured, multi-step process that deterministically finds the correct version.
  2. Ambiguity Detection: A new helper function, CheckForAmbiguousDefaultVersions, is introduced. Before selecting a version, it checks if the API definition contains multiple conflicting "default" versions (e.g., versions named "Default", "default", and ""). This prevents situations where the gateway cannot make a clear choice and returns a specific error.
  3. New Default Version Logic: The GetSingleOrDefaultVersion function implements the new selection logic for non-versioned APIs:
    • If only one version exists, it is chosen by default.
    • If multiple versions exist, it searches for an implicit default version by looking for a version named "Default", then "default", and finally "" (empty string).
    • This ensures a consistent precedence for selecting the default version.
  4. New Status Codes: Two new RequestStatus constants have been added to provide clear feedback when version selection fails:
    • VersionAmbiguousDefault: Returned when multiple implicit default versions are found.
    • VersionDefaultForNotVersionedNotFound: Returned when no suitable single or default version can be found.
  5. Comprehensive Unit Tests: A significant number of unit tests have been added in gateway/api_definition_test.go to validate the new selection logic, covering various scenarios like single versions, implicit defaults, ambiguous configurations, and failure cases.

Affected System Components

  • API Gateway Versioning Middleware: The primary impact is on the gateway's request processing pipeline, specifically the middleware that resolves the API version for an incoming request. The APISpec.Version method is a key component in this process, and its behavior for non-versioned APIs is now stable and predictable.
  • API Definition Handling: The interpretation of the VersionData within an APIDefinition is now more strict. Administrators configuring non-versioned APIs with multiple versions must ensure there is a single, unambiguous default version to avoid request failures.

2. Architecture Visualization

The following flowchart visualizes the updated logic within the APISpec.Version method for handling requests to a non-versioned API.

flowchart TD
    subgraph "APISpec.Version Method (for NotVersioned API)"
        A[Incoming Request] --> B{Check for Ambiguous Defaults};
        B -- Yes --> C[Return Error: VersionAmbiguousDefault];
        B -- No --> D["GetSingleOrDefaultVersion()"];
        
        subgraph "GetSingleOrDefaultVersion Logic"
            D --> E{Only one version exists?};
            E -- Yes --> F[Return the single version];
            E -- No --> G{"Is 'Default' version defined?"};
            G -- Yes --> H["Return 'Default' version"];
            G -- No --> I{"Is 'default' version defined?"};
            I -- Yes --> J["Return 'default' version"];
            I -- No --> K{"Is '' (empty string) version defined?"};
            K -- Yes --> L["Return '' version"];
            K -- No --> M[No default version found];
        end

        F --> Z["Return Version & StatusOk"];
        H --> Z;
        J --> Z;
        L --> Z;

        M --> N[Return Error: VersionDefaultForNotVersionedNotFound];
    end

    C --> X[End Request Processing];
    N --> X;
    Z --> X;
Loading

Powered by Visor from Probelabs

Last updated: 2025-10-01T12:20:01.293Z | Triggered by: synchronize | Commit: 63452ed

Copy link

probelabs bot commented Sep 26, 2025

🔍 Code Analysis Results

Security Issues (1)

Severity Location Issue
🟡 Warning gateway/api_definition.go:1745-1760
The function `CheckForAmbiguousDefaultVersions` iterates over all API versions on every request to a non-versioned API. This introduces an O(N) operation on a critical request path, where N is the number of versions. In a scenario with an extremely large number of versions, this could be a vector for a Denial of Service (DoS) attack.
💡 SuggestionOptimize the check for ambiguity by replacing the inefficient loop with direct map lookups for the specific 'magic' default version names ('Default', 'default', ''). This will make the operation constant time (O(1)) and avoid performance degradation on the hot path, mitigating the theoretical DoS risk.
🔧 Suggested Fix
func (a *APISpec) CheckForAmbiguousDefaultVersions() bool {
	foundDefaultVersions := 0
	if _, ok := a.VersionData.Versions["Default"]; ok {
		foundDefaultVersions++
	}
	if _, ok := a.VersionData.Versions["default"]; ok {
		foundDefaultVersions++
	}
	if _, ok := a.VersionData.Versions[""]; ok {
		foundDefaultVersions++
	}
	return foundDefaultVersions > 1
}

Performance Issues (1)

Severity Location Issue
🟡 Warning gateway/api_definition.go:1745-1760
The `CheckForAmbiguousDefaultVersions` function iterates over the entire `Versions` map on every request to a non-versioned API. This is an O(N) operation, where N is the number of versions defined for the API. For an API with a large number of versions, this loop can introduce unnecessary latency on a critical request path.
💡 SuggestionOptimize the check for ambiguity to be a constant time (O(1)) operation. Instead of iterating over all keys, perform direct map lookups for the three specific keys that define a default version ("Default", "default", and ""). This removes the performance dependency on the number of versions.
🔧 Suggested Fix
func (a *APISpec) CheckForAmbiguousDefaultVersions() bool {
	foundDefaultVersions := 0
	if _, ok := a.VersionData.Versions["Default"]; ok {
		foundDefaultVersions++
	}
	if _, ok := a.VersionData.Versions["default"]; ok {
		foundDefaultVersions++
	}
	if _, ok := a.VersionData.Versions[""]; ok {
		foundDefaultVersions++
	}
return foundDefaultVersions > 1

}

Quality Issues (3)

Severity Location Issue
🟢 Info gateway/api_definition.go:1724-1727
The condition `!a.VersionData.NotVersioned` within `GetSingleOrDefaultVersion` will always evaluate to false because the function is only called from a context where `NotVersioned` is true. This makes the code block unreachable in its current usage and can be confusing for future maintenance.
💡 SuggestionThe comment and the condition should be updated to reflect the actual usage. Since this function is intended for non-versioned APIs, this block appears to be unnecessary. Consider removing it to simplify the function and avoid confusion.
🟢 Info gateway/api_definition.go:1731-1756
The function uses hardcoded strings "Default", "default", and "" to identify implicit default versions. This pattern is repeated in `CheckForAmbiguousDefaultVersions`. Using magic strings can lead to typos and makes the code harder to maintain if these conventional names need to be changed.
💡 SuggestionDefine these values as a slice of constants or variables at the package level (e.g., `var defaultVersionNames = []string{"Default", "default", ""}`) and iterate over that slice in both `GetSingleOrDefaultVersion` and `CheckForAmbiguousDefaultVersions`. This centralizes the logic, improves readability, and follows the DRY (Don't Repeat Yourself) principle.
🟢 Info gateway/api_definition.go:1716-1718
The code iterates over a map with a single element to retrieve its value. While functionally correct in this specific case, relying on map iteration for selection is generally non-deterministic in Go and can be an anti-pattern.
💡 SuggestionAlthough the impact is negligible here, a more idiomatic and deterministic approach would be to iterate to get the key and then access the value directly. This makes the intent clearer and avoids the non-deterministic pattern. For example: `for k := range a.VersionData.Versions { return a.VersionData.Versions[k], true }`.

Style Issues (1)

Severity Location Issue
🟢 Info gateway/api_definition.go:1723-1726
The comment on line 1723 and the associated condition `if !a.VersionData.NotVersioned` are potentially misleading. The function `GetSingleOrDefaultVersion` is only called from a code path where `NotVersioned` is `true`, making this condition always false in its current usage. This could confuse developers about the function's intended scope and behavior.
💡 SuggestionFor better code clarity and maintainability, consider removing the `!a.VersionData.NotVersioned` check and the accompanying comment if this function is exclusively for non-versioned APIs. If it's intended for broader use, the calling logic should be adjusted and the comment clarified.

Dependency Issues (1)

Severity Location Issue
🔴 Critical gateway/api_definition.go:1664-1682
The change to deterministically select a version for non-versioned APIs introduces a breaking change in runtime behavior. Previously, misconfigured APIs might have worked unpredictably; now they will consistently fail if an unambiguous default version cannot be found. This impacts `tyk-operator` and `tyk-portal`, which can now create invalid API definitions, and has critical operational consequences for `tyk-sink` (MDCB) during rolling upgrades.
💡 SuggestionThis change requires coordinated updates and documentation across the ecosystem. 1) A ticket must be raised for `tyk-operator` to add validation to its `ApiDefinition` controller to reject ambiguous configurations. 2) A ticket must be raised for `tyk-portal` to update its UI and backend validation to prevent users from creating such configurations. 3) The release notes for this gateway version must clearly document this change and its operational impact, especially for `tyk-sink` (MDCB) users, advising them to audit API definitions before performing rolling upgrades.

✅ Connectivity Check Passed

No connectivity issues found – changes LGTM.


Powered by Visor from Probelabs

Last updated: 2025-10-01T12:20:02.700Z | Triggered by: synchronize | Commit: 63452ed

@pvormste
Copy link
Collaborator Author

Regarding Visor: The previous behavior of selecting random versions was unintended behavior, therefore it can't be considered a breaking change.

@pvormste pvormste enabled auto-merge (squash) September 30, 2025 12:42
Copy link

sonarqubecloud bot commented Oct 1, 2025

@pvormste pvormste merged commit 14c6d8f into master Oct 1, 2025
46 of 48 checks passed
@pvormste pvormste deleted the fix/TT-15863/fix-random-version-picking-for-not-versioned-api branch October 1, 2025 12:48
@pvormste
Copy link
Collaborator Author

pvormste commented Oct 1, 2025

/release to release-5.10

Copy link

tykbot bot commented Oct 1, 2025

Working on it! Note that it can take a few minutes.

tykbot bot pushed a commit that referenced this pull request Oct 1, 2025
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-15863"
title="TT-15863" target="_blank">TT-15863</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>Random version being picked when not_versioned is set to true</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Bug"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
/>
        Bug
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This PR fixes an issue where a random version was picked for a
non-versioned API.

Instead of picking a random version for non-versioned APIs it will now:
 1. Use the only defined version
2. Use the version called "Default", "default" or "" when multiple
versions are present
 3. Return error when multiple default versions are found
 4. Return error when no version exists

(cherry picked from commit 14c6d8f)
Copy link

tykbot bot commented Oct 1, 2025

@pvormste Seems like there is conflict and it require manual merge.

@pvormste
Copy link
Collaborator Author

pvormste commented Oct 2, 2025

/release to releae-5.8

Copy link

tykbot bot commented Oct 2, 2025

@pvormste Release branch not found

@pvormste
Copy link
Collaborator Author

pvormste commented Oct 2, 2025

/release to release-5.8

Copy link

tykbot bot commented Oct 2, 2025

Working on it! Note that it can take a few minutes.

tykbot bot pushed a commit that referenced this pull request Oct 2, 2025
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-15863"
title="TT-15863" target="_blank">TT-15863</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>Random version being picked when not_versioned is set to true</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Bug"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
/>
        Bug
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This PR fixes an issue where a random version was picked for a
non-versioned API.

Instead of picking a random version for non-versioned APIs it will now:
 1. Use the only defined version
2. Use the version called "Default", "default" or "" when multiple
versions are present
 3. Return error when multiple default versions are found
 4. Return error when no version exists

(cherry picked from commit 14c6d8f)
Copy link

tykbot bot commented Oct 2, 2025

@pvormste Created merge PRs

pvormste added a commit that referenced this pull request Oct 2, 2025
…t versioned API (#7380) (#7394)

### **User description**
[TT-15863] fix random version picking for not versioned API (#7380)

<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-15863"
title="TT-15863" target="_blank">TT-15863</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>Random version being picked when not_versioned is set to true</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Bug"

src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
/>
        Bug
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This PR fixes an issue where a random version was picked for a
non-versioned API.

Instead of picking a random version for non-versioned APIs it will now:
 1. Use the only defined version
2. Use the version called "Default", "default" or "" when multiple
versions are present
 3. Return error when multiple default versions are found
 4. Return error when no version exists

[TT-15863]:
https://tyktech.atlassian.net/browse/TT-15863?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


___

### **PR Type**
Bug fix, Tests


___

### **Description**
- Deterministic version selection for non-versioned APIs

- New request statuses for default version issues

- Helper methods to resolve default/ambiguous versions

- Comprehensive unit tests for new logic


___

### Diagram Walkthrough


```mermaid
flowchart LR
  VersionReq["APISpec.Version(request)"]
  CheckAmb["CheckForAmbiguousDefaultVersions()"]
  GetDef["GetSingleOrDefaultVersion()"]
  Ok["Return StatusOk with version"]
  ErrAmb["Return VersionAmbiguousDefault"]
  ErrDef["Return VersionDefaultForNotVersionedNotFound"]

  VersionReq -- "NotVersioned == true" --> CheckAmb
  CheckAmb -- "ambiguous" --> ErrAmb
  CheckAmb -- "not ambiguous" --> GetDef
  GetDef -- "found" --> Ok
  GetDef -- "not found" --> ErrDef
```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>api_definition.go</strong><dd><code>Deterministic
version resolution and new statuses</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/api_definition.go

<ul><li>Add new RequestStatus values for default version errors<br> <li>
Implement GetSingleOrDefaultVersion helper<br> <li> Implement
CheckForAmbiguousDefaultVersions helper<br> <li> Update Version() to use
deterministic selection for non-versioned APIs</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7394/files#diff-0cf80174bbafb36f6d4f4308ebbd971b2833b76a936bad568220aa1a4ba0ee8b">+92/-34</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>api_definition_test.go</strong><dd><code>Unit tests for
version resolution logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

gateway/api_definition_test.go

<ul><li>Add tests for GetSingleOrDefaultVersion<br> <li> Add tests for
CheckForAmbiguousDefaultVersions<br> <li> Add tests for Version() with
not_versioned scenarios<br> <li> Validate new error statuses and
outcomes</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7394/files#diff-2394daab6fdc5f8dc234699c80c0548947ee3d68d2e33858258d73a8b5eb6f44">+317/-0</a>&nbsp;
</td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___

Co-authored-by: Patric Vormstein <[email protected]>
buger added a commit that referenced this pull request Oct 2, 2025
… versioned API (#7380)

[TT-15863] fix random version picking for not versioned API (#7380)

<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-15863"
title="TT-15863" target="_blank">TT-15863</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
<td>Random version being picked when not_versioned is set to true</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Bug"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium"
/>
        Bug
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Dev</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
      <td>-</td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This PR fixes an issue where a random version was picked for a
non-versioned API.

Instead of picking a random version for non-versioned APIs it will now:
 1. Use the only defined version
2. Use the version called "Default", "default" or "" when multiple
versions are present
 3. Return error when multiple default versions are found
 4. Return error when no version exists
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants